#include "PageCache.h"

// 关于为什么不要在.h里面声明:
//                         .h里面声明类的静态变量要在类外声明,而类外声明如果被多个cpp文件包含,会被重复定义
//                          之后会出现多个同名的重复对象,在链接时会出现严重性错误
PageCache PageCache::_sInst; // 初始化静态变量

//获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
    assert(k > 0);

    //大于128KB的直接向堆申请
    if (k > NPAGES - 1)
    {
        void* ptr = SystemAlloc(k);
        //Span* span = new Span; //脱离new,使用定长内存池的new
        Span* span = _spanPool.New();

        span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
        span->_n = k;
        _idSpanMap[span->_pageId] = span;
        return span;
    }

    //先检查第K个桶里面有没有span
    if (!_spanLists[k].Empty())
    {
        Span* kSpan = _spanLists[k].PopFront();

        // 建立id和span的映射，方便central cache回收小块内存时，查找对应的span
        for (PAGE_ID i = 0; i < kSpan->_n; ++i)
        {
            _idSpanMap[kSpan->_pageId + i] = kSpan;
        }

        return kSpan;
    }

    //检查一下后面的桶里面有没有span,因为大的span如果有,那么可以把它进行切分
    for (size_t i = k + 1/*第k个位置已经检查过了,从下一个开始走*/; i < NPAGES; i++)
    {
        if (!_spanLists[i].Empty())
        {
            Span* nSpan = _spanLists[i].PopFront();
            //Span* kSpan = new Span;
            Span* kSpan = _spanPool.New();


            //在nSpan的头部切k个页下来
            kSpan->_pageId = nSpan->_pageId;
            kSpan->_n = k;

            //nSpan是找到的更大的span,因为采用的是从头开始切,所以页号会往上增大,页数会被分走减小
            //例如原来有一个100页号,10页数的nSpan,后来被切走2页,那么页号的前两个被分走,同时页数也会少两个
            //那么被切分后的nSpan就是页号为102,页数为8的新nSpan
            nSpan->_pageId += k;
            nSpan->_n -= k;

            //将nSpan重新找好桶挂好
            _spanLists[nSpan->_n].PushFront(nSpan);
            //存储nSpan的首尾页号跟nSpan映射
            //因为nSpan没有被Central Cache给使用,所以只需要映射首和尾的页号,方便page cache回收内存时进行的合并查找
            _idSpanMap[nSpan->_pageId] = nSpan;
            _idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;


            //建立每一个页号(pageid)和span的映射,方便central cache回收小块内存时,查找对应的span
            for (PAGE_ID i = 0; i < kSpan->_n; i++)
            {
                _idSpanMap[kSpan->_pageId + i] = kSpan;
            }
            return kSpan;
        }
    }

    //此时PageCache里一个span都没有
    //去堆上申请一个128页的超大内存块
    //Span* bigSpan = new Span;
    Span* bigSpan = _spanPool.New();

    void* ptr = SystemAlloc(NPAGES - 1); // 申请128页
    // ptr是从系统上获取到的内存块的地址,因此该超大内存块的页号其实系统已经给了,只是要自己算出来
    // 通过除一个页面,得到了自己的页号  因为页号×页面就是地址 所以地址÷页面就是页号
    // 这里的页号可能会很大,但是不影响,因为pagecache的桶只看页数个数而不是看页号,页号一是为了反推出地址而是为了判断能否合并
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
    bigSpan->_n = NPAGES - 1;

    //再次提示:span里的_n就是页数,如果有2个那么在PageCache对应的桶就是2page
    _spanLists[bigSpan->_n].PushFront(bigSpan);
    return NewSpan(k);
}

// 返回某一个页号对应的span
Span* PageCache::MapObjectToSpan(void* obj)
{
    //这里的obj就是span的地址,将地址×页面(2^13)就能获得页号(要进行强转用于储存页号)
    PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);

    // PageCache::GetInstance()->_pageMtx.lock(); //对map的访问是不安全的,需要加锁
    std::unique_lock<std::mutex> lock(_pageMtx);  //C++11中的智能指针,其中括号里的锁出了该作用域就会自动解锁

    auto ret = _idSpanMap.find(id);
    if (ret != _idSpanMap.end())
    {
        // PageCache::GetInstance()->_pageMtx.unlock();
        return ret->second;
    }
    else
    {
        assert(false);
        // PageCache::GetInstance()->_pageMtx.unlock();
        return nullptr;
    }
}


void PageCache::ReleaseSpanToPageCache(Span* span)
{
    //判断前后页是否空闲不能用usecount去判断,如果一个span正在被切分,那么它的usecount虽然是0,但是它是被使用中的！！！
    //对span前后的页进行合并,解决内存碎片问题

    //关于合并:如果向OS申请空间了两次,刚好这两次都是连续的空间
    //那么后续合并也是没有问题的

    //如果span里的页数大于128,就说明该申请的空间超过了1M,要直接还给系统
    if (span->_n > NPAGES - 1)
    {
        void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
        // 由于是向系统申请的空间,因此直接还给系统即可,描述该空间的span就使用定长内存池的Delete去回收
        SystemFree(ptr);
        // delete span;
        _spanPool.Delete(span);
    }

    while (true)
    {
        PAGE_ID prevID = span->_pageId - 1;
        auto ret = _idSpanMap.find(prevID);
        // 如果在前面的页号映射关系上没有对应的span,就说明前面的页都不属于内存池里
        if (ret == _idSpanMap.end())
        {
            break;
        }
        //前面相邻页的span在使用,无法合并
        Span* prevSpan = ret->second;
        if (prevSpan->_isUse == true)
        {
            break;
        }
        //合并出超过128页的span没办法管理,不进行合并
        if ((prevSpan->_n + span->_n) > NPAGES - 1)
        {
            break;
        }

        span->_pageId = prevSpan->_pageId;
        span->_n += prevSpan->_n;

        //先将前面的prevspan从spanlist上拿下来
        //为什么不取下目前的这个span呢?因为这个span是从CentralCache拿回来的,根本就没有挂到spanlist上
        _spanLists[prevSpan->_n].Erase(prevSpan);
        // delete prevSpan;
        _spanPool.Delete(prevSpan);
        break;
    }

    //向后合并
    while (true)
    {
        PAGE_ID nextID = span->_pageId + span->_n;
        auto ret = _idSpanMap.find(nextID);
        if (ret == _idSpanMap.end())
        {
            break;
        }

        Span* nextSpan = ret->second;
        if (nextSpan->_isUse == true)
        {
            break;
        }
        if ((nextSpan->_n + span->_n) > NPAGES - 1)
        {
            break;
        }

        span->_n += nextSpan->_n;

        _spanLists[nextSpan->_n].Erase(nextSpan);
        // delete nextSpan;
        _spanPool.Delete(nextSpan);
    }

    //完事后把自己挂到对应位置
    _spanLists[span->_n].PushFront(span);
    span->_isUse = false;
    _idSpanMap[span->_pageId] = span;
    _idSpanMap[span->_pageId + span->_n - 1] = span;

}
